Premium Analytics: port the Locations stats widget#49513
Conversation
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
There was a problem hiding this comment.
Pull request overview
Ports Jetpack Stats’ Locations module into Premium Analytics as a new jpa/locations dashboard widget, including UI (GeoChart + ranked list), a widget-local data hook/normalizer, and the server-side blog-id bridge needed to call the stats-admin proxy route.
Changes:
- Add the
jpa/locationswidget (metadata/type definition, render UI, styles, README, and widget-local package manifest). - Add a
useLocationViewshook that fetches/jetpack/v4/stats-app/sites/{blogId}/stats/country-viewsand normalizes the summarized response, with a sample-data fallback. - Expose the connected WordPress.com blog id on the dashboard page via
window.configData.blog_idand wire required JS deps (@automattic/charts,@wordpress/api-fetch) + lockfile.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| projects/packages/premium-analytics/widgets/locations/widget.ts | Declares the widget type, icon, presentation, and attributes (period/max). |
| projects/packages/premium-analytics/widgets/locations/widget.json | Adds widget metadata for discovery/inserter (title/description/category). |
| projects/packages/premium-analytics/widgets/locations/use-location-views.ts | Implements fetch + normalization + sample-data fallback for country views. |
| projects/packages/premium-analytics/widgets/locations/style.module.css | Styles for widget layout, placeholder, list rows, and sample badge. |
| projects/packages/premium-analytics/widgets/locations/render.tsx | Renders the GeoChart + ranked country list with loading/empty states. |
| projects/packages/premium-analytics/widgets/locations/README.md | Documents scope, upstream deviations, and data/API dependencies. |
| projects/packages/premium-analytics/widgets/locations/package.json | Declares widget-local dependencies (charts, api-fetch, ui, etc.). |
| projects/packages/premium-analytics/src/stats-config.php | Adds inline config to expose window.configData.blog_id on the dashboard page. |
| projects/packages/premium-analytics/src/class-analytics.php | Loads the new stats-config bridge during package init. |
| projects/packages/premium-analytics/package.json | Adds @automattic/charts and @wordpress/api-fetch to package deps. |
| projects/packages/premium-analytics/changelog/add-locations-widget | Adds a changelog entry for the new widget. |
| pnpm-lock.yaml | Updates lockfile for the new/updated workspace dependencies. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
| rows.push( { | ||
| // ’ in country names breaks the geo visualization, so normalize it. | ||
| label: view.location || info.country_full.replace( /’/g, "'" ), | ||
| countryCode: view.country_code, | ||
| countryFull: info.country_full, | ||
| value: view.views, | ||
| region: info.map_region ?? '', | ||
| } ); |
| // is demoable. See README for the package support this needs. | ||
| if ( ! blogId ) { | ||
| setState( { | ||
| data: SAMPLE_LOCATIONS.slice( 0, max ), |
| return; | ||
| } | ||
| setState( { | ||
| data: SAMPLE_LOCATIONS.slice( 0, max ), |
91aa9fb to
909996e
Compare
ea86b35 to
d4fdf61
Compare
| rows.push( { | ||
| // ’ in country names breaks the geo visualization, so normalize it. | ||
| label: view.location || info.country_full.replace( /’/g, "'" ), | ||
| countryCode: view.country_code, | ||
| countryFull: info.country_full, | ||
| value: view.views, | ||
| region: info.map_region ?? '', | ||
| } ); |
| if ( ! blogId ) { | ||
| setState( { | ||
| data: SAMPLE_LOCATIONS.slice( 0, max ), | ||
| isLoading: false, | ||
| isError: false, | ||
| isSample: true, | ||
| } ); | ||
| return; | ||
| } |
| .catch( () => { | ||
| if ( cancelled ) { | ||
| return; | ||
| } | ||
| setState( { | ||
| data: SAMPLE_LOCATIONS.slice( 0, max ), | ||
| isLoading: false, | ||
| isError: true, | ||
| isSample: true, | ||
| } ); | ||
| } ); |
Private, source-consumed copy of @wordpress/grid until core publishes it.
Private, source-consumed copy of @wordpress/widget-primitives until core publishes it.
Private, source-consumed dashboard engine; depends on jetpack-widget-primitives and jetpack-grid.
Renders the WidgetDashboard engine via wp-build, bundling the grid/primitives/dashboard packages.
useWidgetTypes drives discovery via /wp/v2/widget-modules; hello-world builds as a lazy-loaded script module.
Hook the page boot-dependencies filter so widget modules reach the import map; move the endpoint to the jetpack/v4 namespace.
@wordpress/grid and widget-primitives references in widget-dashboard JSDoc/comments/README, per the faithful-source sync policy.
909996e to
d060b2d
Compare
Port the Jetpack Stats Locations module (country-level v1) from Calypso into the Premium Analytics customizable dashboard as a jpa/locations widget: GeoChart world map + ranked country list, fetching live data through the stats-admin proxy. Expose the connected blog id to the dashboard page (src/stats-config.php) so stats widgets can build their proxy requests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the period attribute with date-range presets (today / 7 / 30 / 365 days) summarized server-side, and omit the date param so the window anchors to today in the site's own timezone. period=day with num=1 showed only the current UTC day, which renders empty most mornings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the plain country list with the widgets-toolkit LeaderboardChart (flags, share bars) beside the map, wrapped in GlobalChartsProvider since LeaderboardChart does not self-provide the charts context the way GeoChart does. Wire internal-package resolution for widget code via link:packages/* deps on the parent manifest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cae7f67 to
89d5fc8
Compare
d060b2d to
5bb6dd9
Compare
Fixes #
Proposed changes
Ports the Locations module from Jetpack Stats (Calypso's
client/my-sites/stats/features/modules/stats-locations) into the Premium Analytics customizable dashboard as ajpa/locationswidget, on the dashboard introduced by #49502.widgets/locations/): world map (@automattic/chartsGeoChart) + a country leaderboard (widgets-toolkitLeaderboardChart: flags, share bars), presented like the visitors-by-location widget. Attributes:range(trailing-window presets, default last 30 days;dateis omitted so the endpoint anchors "today" in the site's timezone) andmax. Loading/empty states and a bundled sample-data fallback (marked "Sample data") when live data isn't available.GET /jetpack/v4/stats-app/sites/{blogId}/stats/country-views(summarized), normalized by a widget-local port of Calypso'sstatsCountryViewsnormalizer (country/summary branch).src/stats-config.php): exposes the connected site's blog id to the dashboard page aswindow.configData.blog_id(the Odyssey convention) via an inline script onwp-api-fetch, hooked to both page-init actions. Stats widgets need the id because the proxy bakes it into the route. No-ops (→ sample data) when the site isn't connected.link:packages/*deps on the parent manifest, so widgets can consume@jetpack-premium-analytics/widgets-toolkit; widgets resolve imports against the parent manifest.Deviations from upstream (documented in the widget README)
…/stats/location-views/(country|region|city)route makes them reachable when we get there.QuerySiteStats,calypso-router, the gating/upsell flow, the Jetpack-version upgrade prompt, and analytics event tracking.@automattic/chartsGeoChart(react-google-charts) instead of Calypso's hand-rolled Google Charts loader; countries resolution needs no Maps API key.jpa/locations(host namespace).Known limitations / follow-ups
title/attribute labels inwidget.json/widget.tsaren't translatable yet — pending an i18n convention for the widget contract (same situation ashello-world).Related product discussion/links
Does this pull request change what data or activity we track or use?
No.
Testing instructions
Requires pnpm ^11.5 and Node ^24.15. After checkout, run
pnpm installto reconcile the lockfile.pnpm --filter @automattic/jetpack-premium-analytics build—hello-worldandlocationswidgets both build, andbuild/widgets/registry.phpregistersjpa/locations.pnpm --filter @automattic/jetpack-premium-analytics typecheck— only the pre-existinguseSelect/preferences errors inwidget-dashboard/widget-primitivesremain (present on the base branch; verified with the widget removed).window.configData.blog_id = <wpcom blog id>;is inlined beforewp-api-fetchruns.GET /wp-json/jetpack/v4/widget-moduleslistsjpa/locations;widgets/locations/render.min.jsloads as a separate chunk on mount; one…/stats-app/sites/{id}/stats/country-views?period=day&…&summarize=1request fires.